package com.zzb.framework.starter.xxljob.conf.datasource;

import com.baomidou.dynamic.datasource.aop.DynamicAspectJExpressionPointcut;
import com.baomidou.dynamic.datasource.aop.DynamicJdkRegexpMethodPointcut;
import com.baomidou.dynamic.datasource.matcher.ExpressionMatcher;
import com.baomidou.dynamic.datasource.matcher.Matcher;
import com.baomidou.dynamic.datasource.matcher.RegexMatcher;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@Slf4j
public class PdDynamicDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware  {
    /**
     * SPEL参数标识
     */
    private static final String DYNAMIC_PREFIX = "#";

    @Setter
    private DsProcessor dsProcessor;

    private Advice advice;

    private Pointcut pointcut;

    private Map<String, String> matchesCache = new HashMap<String, String>();

    public PdDynamicDataSourceAdvisor(List<Matcher> matchers) {
        this.pointcut = buildPointcut(matchers);
        this.advice = buildAdvice();
    }

    private Advice buildAdvice() {
        return (MethodInterceptor) invocation -> {
            String key = null;
            try {
                Method method = invocation.getMethod();
                String methodPath = method.getDeclaringClass().getName() + "." + method.getName();
                log.debug("PdDynamicDataSourceAdvisor methodPath : {}" ,methodPath );
                key = matchesCache.get(methodPath);
                if(key == null) {
                    if (invocation instanceof ReflectiveMethodInvocation) {
                        methodPath =method.getDeclaringClass().getName().replace(".","\\.") + "\\$\\$EnhancerBySpringCGLIB\\$\\$(.*)\\." + method.getName();
                        Pattern pattern = Pattern.compile(methodPath);
                        String matchesCacheKey = matchesCache.keySet().stream().filter(s -> {
                            java.util.regex.Matcher matcher = pattern.matcher(s);
                            return matcher.find();
                        }).findFirst().orElse(null);
                        if(matchesCacheKey != null){
                            key = matchesCache.get(matchesCacheKey);
                        } else {
                            ReflectiveMethodInvocation reflectiveMethodInvocation = (ReflectiveMethodInvocation) invocation;
                            log.warn("datasource inject error : methodPath is {} ,  method.class is {}, method.name is {} , proxy class is {} "
                                    ,methodPath,method.getDeclaringClass().getName(), method.getName(),reflectiveMethodInvocation.getProxy().getClass());
                        }
                    } else {
                        log.warn("datasource inject error : invocation is not a instance of ReflectiveMethodInvocation, and methodPath is {}",methodPath);
                    }
                }
                if (key != null && !key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) {
                    key = dsProcessor.determineDatasource(invocation, key);
                }
                if(key != null) {
                    DynamicDataSourceContextHolder.push(key);
                }
                return invocation.proceed();
            } finally {
                if(key != null) {
                    DynamicDataSourceContextHolder.poll();
                }
            }
        };
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
        }
    }

    private Pointcut buildPointcut(List<Matcher> matchers) {
        ComposablePointcut composablePointcut = null;
        for (Matcher matcher : matchers) {
            if (matcher instanceof RegexMatcher) {
                RegexMatcher regexMatcher = (RegexMatcher) matcher;
                Pointcut pointcut = new DynamicJdkRegexpMethodPointcut(regexMatcher.getPattern(), regexMatcher.getDs(), matchesCache);
                if (composablePointcut == null) {
                    composablePointcut = new ComposablePointcut(pointcut);
                } else {
                    composablePointcut.union(pointcut);
                }
            } else {
                ExpressionMatcher expressionMatcher = (ExpressionMatcher) matcher;
                Pointcut pointcut = new DynamicAspectJExpressionPointcut(expressionMatcher.getExpression(), expressionMatcher.getDs(), matchesCache);
                if (composablePointcut == null) {
                    composablePointcut = new ComposablePointcut(pointcut);
                } else {
                    composablePointcut.union(pointcut);
                }
            }
        }
        return composablePointcut;
    }

    @Override
    public void setOrder(int order) {
        super.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
}
